package org.hong.monkey.crawl

import java.io._

import com.mongodb.BasicDBObject
import com.mongodb.util.JSON
import org.hong.monkey.util.Utils
import org.hong.monkey.MonkeyException
import org.hong.monkey.network.util.JavaUtils

import scala.concurrent.ExecutionContext
import scala.util.Random

/**
 * Created by wangsihong on 2016/10/31.
 */

class Headers extends Serializable {

  @transient
  private var headers = new BasicDBObject()

  def put(key:String, value:String) = headers.put(key, value)

  def get(key:String) = headers.getString(key)

  def remove(key:String) = headers.remove(key)

  def keySet() = headers.keySet()

  /** Used by the JVM when serializing this object. */
  private def writeObject(out: ObjectOutputStream): Unit = Utils.tryOrIOException {
    val jsonBytes = headers.toString.getBytes()
    out.writeInt(jsonBytes.length)
    out.write(jsonBytes)
  }

  /** Used by the JVM when deserializing this object. */
  private def readObject(in: ObjectInputStream): Unit = Utils.tryOrIOException {
    val len = in.readInt()
    val jsonBytes = new Array[Byte](len)
    in.readFully(jsonBytes)
    val json = new String(jsonBytes, "UTF-8")
    headers = JSON.parse(json).asInstanceOf[BasicDBObject]
  }

}

case class CrawlRequest (
  link: String,
  cookies: String = null,
  headers: Headers = null,
  timeout: String = "60s",
  proxy: HttpDownloaderProxy = null,
  carryData: Any = None,
  retryTimes: Int = 0,
  retryConditionFunction: CrawlResponse => Boolean = {response => response.responseCode != 200},
  delayTime:String = "0s",
  randomDelayMinLimitTime:String = "0s",
  randomDelayMaxLimitTime:String = "0s",
  downloaderClass: String = "org.hong.monkey.crawl.HttpClientDownloader",
  spiderkitGroup: String = "") extends Serializable {

  def this (
    link: String,
    cookies: String,
    headers: Headers,
    timeout: String,
    proxy: HttpDownloaderProxy,
    carryData: Any,
    retryTimes: Int,
    delayTime:String,
    randomDelayMinLimitTime:String,
    randomDelayMaxLimitTime:String,
    downloaderClass: String
    ) = this(link, cookies, headers, timeout, proxy, carryData, retryTimes,
    {response => response.responseCode != 200}, delayTime, randomDelayMinLimitTime,
    randomDelayMaxLimitTime, downloaderClass)

  def download(): CrawlResponse = {

    val delay = JavaUtils.timeStringAsMs(delayTime)
    if (delay > 0) {
      Thread.sleep(delay)
    } else {
      val delayMin = JavaUtils.timeStringAsMs(randomDelayMinLimitTime)
      val delayMax = JavaUtils.timeStringAsMs(randomDelayMaxLimitTime)
      val randomDelay = randomInRange(delayMin, delayMax)
      if (randomDelay > 0)
        Thread.sleep(randomDelay)
    }

    val downloader = HttpDownloaderManager.createDownloaderByClass(this.downloaderClass)
    var response = downloader.get(this)(HttpDownloaderManager.executor)
    if (this.retryTimes > 0 && retryConditionFunction(response)) {
      var times = this.retryTimes
      var retry = true
      while (retry) {
        response = downloader.get(this)(HttpDownloaderManager.executor)
        times -= 1
        if (times <= 0 || !retryConditionFunction(response))
          retry = false
      }
    }
    response
  }

  private def randomInRange(min: Long, max: Long): Long = {
    if (min >= 0 && max > 0 && max >= min) {
      if (max == min) {
        max
      } else {
        val random = new Random()
        val nextDouble = random.nextDouble()
        Math.ceil(nextDouble * (max - min) + min).toLong
      }
    } else 0L
  }

  override def toString: String =
    s"link : $link, timeout: $timeout ${JavaUtils.timeStringAsSec(timeout)}s\ncarryData=$carryData"
}

case class CrawlResponse (
  link: String,
  cookies : String,
  headers : Headers,
  responseCode: Int,
  exception: MonkeyDownloaderException,
  buffer: Array[Byte],
  proxy: HttpDownloaderProxy,
  carryData: Any = None) extends Serializable {

  def responseString(coding: String): String = {
    new String(this.buffer, coding)
  }

  def responseUTF8String(): String = {
    new String(this.buffer, "UTF-8")
  }

  def responseGBKString(): String = {
    new String(this.buffer, "GBK")
  }

  override def toString(): String =
    s"link : $link, responseCode:$responseCode\ncarryData=$carryData"
}

private[monkey] class MonkeyDownloaderException(cause: Throwable)
  extends MonkeyException("Downloader error", cause)



